package cl.solidit.geomanagement.persistance;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import cl.solidit.geomanagement.persistance.model.Comuna;
import cl.solidit.geomanagement.persistance.model.DisponibilidadVisita;
import cl.solidit.geomanagement.persistance.model.EstadoPropiedad;
import cl.solidit.geomanagement.persistance.model.Persona;
import cl.solidit.geomanagement.persistance.model.PreguntaFrecuente;
import cl.solidit.geomanagement.persistance.model.PreguntaRealizada;
import cl.solidit.geomanagement.persistance.model.Propiedad;
import cl.solidit.geomanagement.persistance.model.TipoPropiedad;
import cl.solidit.geomanagement.persistance.model.Upload;
import cl.solidit.geomanagement.persistance.model.UploadLog;
import cl.solidit.geomanagement.persistance.util.FiltroPropiedades;
import cl.solidit.geomanagement.persistance.util.Rut;

@Repository
public class PropiedadDAO {
	static final private Integer HOUSE = 1;
	static final private Integer APP = 2;
	static final private Integer AGR = 3;
	static final private Integer BOD = 4;
	static final private Integer COM = 5;
	static final private Integer EST = 6;
	static final private Integer OFI = 7;
	static final private Integer SIT = 8;
	static final private Integer MIN = 9;
	static final private Logger logger = Logger.getLogger(PropiedadDAO.class);

	@PersistenceContext
	private EntityManager em;
	@Autowired
	private ComunaDAO comunaDAO;
	@Autowired
	private PersonaDAO personaDAO;
	@Autowired
	private UploadDAO uploadDAO;

	@Transactional
	public void createProperty(Propiedad propiedad) {
		if (propiedad.getNegocioExterno() == null)
			propiedad.setNegocioExterno(false);
		if (propiedad.getId() != null && propiedad.getId() > 0)
			em.merge(propiedad);
		else {
			em.persist(propiedad);
		}
		propiedad.setNumero(propiedad.getId());
		if (propiedad.getLatitud() != null && propiedad.getLongitud() != null)
			while (this.consultarPorPosicion(propiedad.getLongitud(),
					propiedad.getLatitud()) > 1) {

				BigDecimal offset = (new BigDecimal(new Random().nextDouble()
						+ "").multiply(new BigDecimal("0.00001")))
						.add(new BigDecimal("0.000005"));
				BigDecimal lat;
				BigDecimal lng;
				if (new Random().nextBoolean())
					lat = new BigDecimal(propiedad.getLatitud()).add(offset);
				else
					lat = new BigDecimal(propiedad.getLatitud())
							.subtract(offset);
				if (new Random().nextBoolean())
					lng = new BigDecimal(propiedad.getLongitud()).add(offset);
				else
					lng = new BigDecimal(propiedad.getLongitud())
							.subtract(offset);
				propiedad.setLongitud(lng.toString());
				propiedad.setLatitud(lat.toString());
			}
	}

	@Transactional
	public Propiedad findById(Long idPropiedad) {
		return em.find(Propiedad.class, idPropiedad);

	}

	@Transactional
	public Propiedad findByPropietarioId(Long propietario) {
		TypedQuery<Propiedad> q = em.createQuery(
				"From Propiedad u where u.propietario=:propietario",
				Propiedad.class);
		q.setParameter("propietario", propietario);
		List<Propiedad> resultado = q.getResultList();
		if (resultado.size() == 0)
			return null;
		return resultado.get(0);
	}
	@Transactional
	public List<Propiedad> findAllPropertys(){
		TypedQuery<Propiedad> q = em.createQuery("From Propiedad", Propiedad.class);
		List<Propiedad> resultado = q.getResultList();
		return resultado;
	}

	@Transactional
	public Propiedad consultarPropiedad(Long numeroPropiedad) {
		// return em.find(Propiedad.class, idPropiedad);
		TypedQuery<Propiedad> q = em.createQuery(
				"From Propiedad u where u.numero=:numero", Propiedad.class);
		q.setParameter("numero", numeroPropiedad);
		List<Propiedad> resultado = q.getResultList();
		if (resultado.size() == 0)
			return null;
		return resultado.get(0);
	}

	@Transactional
	public Long consultarPorPosicion(String longitud, String latitud) {
		TypedQuery<Long> q = em
				.createQuery(
						"select count(*) From Propiedad u where u.longitud=:longitud and u.latitud=:latitud",
						Long.class);
		q.setParameter("longitud", longitud);
		q.setParameter("latitud", latitud);
		return q.getSingleResult();
	}

	@Transactional
	public List<TipoPropiedad> traerTipoPropiedad() {
		TypedQuery<TipoPropiedad> q = em.createQuery("From TipoPropiedad",
				TipoPropiedad.class);
		List<TipoPropiedad> resultado = q.getResultList();
		return resultado;

	}

	@Transactional
	public List<DisponibilidadVisita> traerDisponibilidad() {
		TypedQuery<DisponibilidadVisita> q = em.createQuery(
				"From DisponibilidadVisita", DisponibilidadVisita.class);
		List<DisponibilidadVisita> resultado = q.getResultList();
		return resultado;

	}

	@Transactional
	public List<PreguntaFrecuente> traerPreguntaFrecuente() {
		TypedQuery<PreguntaFrecuente> q = em.createQuery(
				"From PreguntaFrecuente", PreguntaFrecuente.class);
		List<PreguntaFrecuente> resultado = q.getResultList();
		return resultado;

	}

	@Transactional
	public List<PreguntaRealizada> traerPreguntaRealizada() {
		TypedQuery<PreguntaRealizada> q = em.createQuery(
				"From PreguntaRealizada", PreguntaRealizada.class);
		List<PreguntaRealizada> resultado = q.getResultList();
		return resultado;

	}

	@Transactional
	public List<Propiedad> findPropiedadSinGeoreferencia() {
		TypedQuery<Propiedad> q = em
				.createQuery(
						"From Propiedad  where latitud IS NULL and longitud IS NULL order by numero",
						Propiedad.class);
		List<Propiedad> resultado = q.getResultList();
		return resultado;
	}

	@Transactional
	public List<Propiedad> findPropiedadConGeoreferencia() {
		TypedQuery<Propiedad> q = em
				.createQuery(
						"From Propiedad  where latitud IS NOT NULL and longitud IS NOT NULL order by numero",
						Propiedad.class);
		List<Propiedad> resultado = q.getResultList();
		return resultado;
	}

	@Transactional
	public List<Propiedad> search(FiltroPropiedades filtro) {
		String hql = "From Propiedad p WHERE p.latitud is not null and p.longitud is not null and "
				+ this.queryTipos(filtro)
				+ " AND "
				+ queryDisponibilidads(filtro)
				+ " AND "
				+ queryAmoblado(filtro)
				+ " AND "
				+ queryPercepcion(filtro)
				+ " AND " + queryOfertada(filtro);
		if (filtro.getFrom() != null) {
			hql += " AND p.ultimaVisita >= :from";
		}
		if (filtro.getTo() != null) {
			hql += " AND p.ultimaVisita < :to";
		}
		if (filtro.getRut() != null && filtro.getRut().trim().length() > 0) {
			hql += " AND p.agente.persona.rut=:rut";
		}
		if(filtro.getNumero()!= null){
			hql +=" AND p.numero=:numero";
		}
		if(filtro.getComuna()!=null){
			hql +=" AND p.comuna.id=:comuna";
		}
		if (filtro.getDoGeoFilter()) {
			Query q = em
					.createNativeQuery("SELECT id FROM propiedad WHERE ST_Within(georef, ST_GeometryFromText ('POLYGON("
							+ filtro.getBoundingBox() + ")', 4326));");
			@SuppressWarnings("unchecked")
			List<BigInteger> results = q.getResultList();
			if (results.size() > 0)
				hql += " AND p.id in (" + StringUtils.join(results, ",") + ")";
			else
				hql += " AND p.id < 0";
		}
		TypedQuery<Propiedad> query = em.createQuery(hql, Propiedad.class);
		if (filtro.getFrom() != null) {
			query.setParameter("from", filtro.getFrom());
		}
		if (filtro.getTo() != null) {
			query.setParameter("to", DateUtils.addDays(filtro.getTo(), 1));
		}
		if (filtro.getRut() != null && filtro.getRut().trim().length() > 0) {
			query.setParameter("rut", filtro.getRut());
		}
		if(filtro.getNumero() !=null){
			query.setParameter("numero", filtro.getNumero());
		}
		if(filtro.getComuna()!=null){
			query.setParameter("comuna", filtro.getComuna());
		}
		return query.getResultList();
	}

	public Long countSearchResults(FiltroPropiedades filtro) {
		// TODO Factorisar este metodo que es casi identico al search
		String hql = "select count(*) From Propiedad p WHERE p.latitud is not null and p.longitud is not null and "
				+ this.queryTipos(filtro)
				+ " AND "
				+ queryDisponibilidads(filtro)
				+ " AND "
				+ queryAmoblado(filtro)
				+ " AND "
				+ queryPercepcion(filtro)
				+ " AND " + queryOfertada(filtro);
		if (filtro.getFrom() != null) {
			hql += " AND p.ultimaVisita >= :from";
		}
		if (filtro.getTo() != null) {
			hql += " AND p.ultimaVisita < :to";
		}
		if (filtro.getRut() != null && filtro.getRut().trim().length() > 0) {
			hql += " AND p.agente.persona.rut=:rut";
		}
		if(filtro.getNumero()!= null){
			hql +=" AND p.numero=:numero";
		}
		if(filtro.getComuna()!= null){
			hql +=" AND p.comuna.id=:comuna";
		}

		TypedQuery<Long> query = em.createQuery(hql, Long.class);
		if (filtro.getFrom() != null) {
			query.setParameter("from", filtro.getFrom());
		}
		if (filtro.getTo() != null) {
			query.setParameter("to", DateUtils.addDays(filtro.getTo(), 1));
		}
		if (filtro.getRut() != null && filtro.getRut().trim().length() > 0) {
			query.setParameter("rut", filtro.getRut());
		}
		if(filtro.getNumero()!=null){
			query.setParameter("numero", filtro.getNumero());
		}
		if (filtro.getComuna() != null ) {
			query.setParameter("comuna", filtro.getComuna());
		}
		return query.getSingleResult();
	}

	@Transactional
	private String queryTipos(FiltroPropiedades filtro) {
		List<Integer> query = new ArrayList<Integer>();
		if (filtro.getHouse())
			query.add(HOUSE);
		if (filtro.getApp())
			query.add(APP);
		if (filtro.getAgr())
			query.add(AGR);
		if (filtro.getBod())
			query.add(BOD);
		if (filtro.getCom())
			query.add(COM);
		if (filtro.getEst())
			query.add(EST);
		if (filtro.getOfi())
			query.add(OFI);
		if (filtro.getSit())
			query.add(SIT);
		if (filtro.getMin())
			query.add(MIN);
		if (query.size() > 0)
			return "p.tipoPropiedad.id in (" + StringUtils.join(query, ",")
					+ ")";
		// esta condicion nunca se cumplira. Con esto se cumple el
		// comportamiento deseado de que no seleccionar ningua significa no ver
		// ninguna
		return "p.tipoPropiedad is null";
	}

	@Transactional
	private String queryDisponibilidads(FiltroPropiedades filtro) {
		if (filtro.getAvalible() && filtro.getNotAvalible()
				&& filtro.getReserved()) {
			return "p.disponible is not null";// siempre se cumple;
		}
		if (filtro.getAvalible()) {
			return "p.disponible = TRUE";
		}
		if (filtro.getNotAvalible()) {
			return "p.disponible = FALSE";
		}
		if (filtro.getReserved()) {
			return "p.disponible = FALSE";
		}
		// esta condicion nunca se cumplira. Con esto se cumple el
		// comportamiento deseado de que no seleccionar ningua significa no ver
		// ninguna
		return "p.disponible is null";
	}

	@Transactional
	private String queryAmoblado(FiltroPropiedades filtro) {
		if (filtro.getFurnitured() && filtro.getNotFurnitured()) {
			return "p.amoblada is not null";// siempre se cumple;
		}
		if (filtro.getFurnitured()) {
			return "p.amoblada = TRUE";
		}
		if (filtro.getNotFurnitured()) {
			return "p.amoblada = FALSE";
		}
		// esta condicion nunca se cumplira. Con esto se cumple el
		// comportamiento deseado de que no seleccionar ningua significa no ver
		// ninguna
		return "p.amoblada is null";
	}

	@Transactional
	private String queryOfertada(FiltroPropiedades filtro) {
		if (filtro.getOffer() && filtro.getNotOffer()) {
			return "p.ofertada is not null";
		}
		if (filtro.getOffer()) {
			return "p.ofertada = TRUE";
		}
		if (filtro.getNotOffer()) {
			return "p.ofertada = FALSE";
		}
		return "p.ofertada is null";
	}
	



	@Transactional
	private String queryPercepcion(FiltroPropiedades filtro) {
		List<Integer> query = new ArrayList<Integer>();
		if (filtro.getLow())
			query.add(1);
		if (filtro.getMid())
			query.add(2);
		if (filtro.getHi())
			query.add(3);
		if (query.size() > 0)
			return "p.percepcion in (" + StringUtils.join(query, ",") + ")";
		// esta condicion nunca se cumplira. Con esto se cumple el
		// comportamiento deseado de que no seleccionar ningua significa no ver
		// ninguna
		return "p.percepcion<0";
	}
	@Transactional
	public void loadPropiedad(Long upload, String[] datos) {
		// String ncomuna = datos[2];
		// Comuna comuna = comunaDAO.findByName(ncomuna.toUpperCase());
		Upload u = uploadDAO.byId(upload);
		// if (comuna == null) {
		// u.getLogs().add(
		// new UploadLog(u, "La comuna " + ncomuna + " no existe"));
		// u.setError(u.getError() + 1);
		// } else {
		try {
			Persona propietario = this.checkRut(u, datos[6]);
			Persona captador = this.checkRut(u, datos[7]);
			Persona agente = this.checkRut(u, datos[9]);
			if (agente != null && agente.getUsuario() == null) {
				throw new RuntimeException("El usuario con rut " + datos[9]
						+ " no esta registrado");
			}
			Long numero = Long.parseLong(datos[0]);
			Propiedad p = this.consultarPropiedad(numero);
			boolean newPropiedad = false;
			if (p == null) {
				newPropiedad = true;
				p = new Propiedad();
				p.setNumero(numero);
				p.setFechaIngreso(new Date());
			}
			p.setDireccion(datos[1]);
			p.setComuna(new Comuna(Long.parseLong(datos[2].trim())));
			p.setTipoPropiedad(new TipoPropiedad(
					Long.parseLong(datos[3].trim())));
			p.setVenta(datos[4].trim().equals("2"));
			p.setAmoblada(datos[5].trim().equalsIgnoreCase("true"));
			p.setAgente(agente != null ? agente.getUsuario() : null);
			p.setPropietario(propietario);
			p.setCaptador(captador);
			p.setPropietario(propietario);
			p.setMetrosCuadrados(new BigDecimal(datos[8].trim()
					.replaceAll("\\.", "").replaceAll(",", ".")));
			p.setEstado(new EstadoPropiedad(Integer.parseInt(datos[10].trim())));
			p.setDisponible(datos[10].trim().equals("1"));
			p.setNegocioExterno(datos[11].trim().equalsIgnoreCase("false"));
			p.setDetalle(datos[12].trim().replaceAll("<b/>", "\n"));
			p.setValor(new BigDecimal(datos[13].trim().replaceAll("\\.", "")
					.replaceAll(",", ".")));
			p.setValorEnUf(datos[14].trim().equalsIgnoreCase("true"));

			if (newPropiedad) {
				this.createProperty(p);
				u.setInserted(u.getInserted() + 1);
			} else {
				u.setUpdated(u.getUpdated() + 1);
			}

		} catch (Exception e) {
			u.getLogs().add(
					new UploadLog(u, e.getClass().getName() + ":"
							+ e.getMessage()));
			u.setError(u.getError() + 1);
		}

		u.setDone(u.getDone() + 1);

	}

	@Transactional
	private Persona checkRut(Upload u, String rut) {
		if (rut == null || rut.trim().length() == 0)
			return null;
		rut = Rut.format(rut);
		Persona p = this.personaDAO.consultarPersonaByRutParcial(Integer
				.parseInt(rut));
		if (p == null)
			throw new RuntimeException("No existe la persona con el rut " + rut);
		return p;

	}

}
